import {
    combineReducers,
    createStore,
    applyMiddleware,
    compose
} from 'redux'
import {
    Provider,
    connect as reduxConnect,
    useDispatch as useReduxDispatch,
    useSelector as useReduxSelector,
    useStore as useReduxStore
} from 'react-redux'

export const connect = reduxConnect

const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;

const SelectorActionType = '@@MOUNT_SELECTOR'
const selectorAction = (selector) => ({
    type: SelectorActionType,
    payload: selector
})
function selectorReducer(state=Object.create(null), action) {
    if (action.type === SelectorActionType) {
        return action.payload
    } else {
        return state
    }
}

function ReactStore (config) {
    this.config = config
    this.Provider = Provider
    this.getState = () => void 0
    this.subscribe = (handler) => handler
    this.replaceReducer = () => void 0
    this.initialized = false
    Object.defineProperty(this, 'state', {
        get: () => {
            return this.getState()
        }
    })

    this.init = () => {
        const {
            selector,
            actions,
            watchers,
            enhancers,
            actionTypes,
            middlewares,
            baseReducers,
            moduleReducers,
            preloadedState,
        } = resolveStoreConfig(this.config)
        moduleReducers.selector = selectorReducer // 挂载selector
        const rootReducer = createRootReducer(baseReducers, moduleReducers).bind(this)
        this.actionTypes = actionTypes;
        this.store = createStore(
            rootReducer,
            preloadedState,
            composeEnhancers(
                applyMiddleware(...middlewares),
                ...enhancers,
            )
        )
        this.commit = this.store.dispatch
        this.getState = this.store.getState
        this.subscribe = this.store.subscribe
        this.replaceReducer = this.store.replaceReducer
        this.actions = generateActions(actions, this.dispatch)
        this.selector = generateSelector(selector, this.store)
        this.commit(selectorAction(this.selector))
        this.watchers = watchers
        this.prevWatchValues = getValuesInState(this.state, Object.keys(watchers))
        this.subscribe(this.handleStateChange)
        this.initialized = true
    }

    this.dispatch = (type, payload = undefined) => {
        const actionTypes = this.actionTypes
        const actions = this.actions
        const meta = arguments[2]
        if (actionTypes[type] && typeof actionTypes[type] === 'function') {
            const typeArr = type.split('/')
            let actionsScope;
            if (typeof actions[typeArr[0]] === 'object') {
                actionsScope = actions[typeArr[0]]
            } else {
                actionsScope = actions;
            }
            return actionTypes[type].call(actionsScope,  payload, this, meta)
        } else {
            let action;
            if (typeof type === 'object' && typeof type.type === 'string') {
                action = type
            } else {
                action = { type, payload, meta }
            }
            return this.commit(action)
        }
    }

    this.handleStateChange = () =>{
        const state = this.state
        for (let key in this.watchers) {
            const curValue = getStateIn(state, key)
            const prevValue = this.prevWatchValues[key];
            if (curValue !== prevValue) {
                this.prevWatchValues[key] = curValue
                const watcher = this.watchers[key];
                if (watcher.namespace) {
                    watcher.call(this.actions[watcher.namespace], curValue, prevValue, this)
                } else {
                    watcher.call(this.actions, curValue, prevValue, this)
                }
            }
        }
    }

    this.addModule = (name, moduleObj) => {
        if (!this.config.modules) {
            this.config.modules = {}
        }
        this.config.modules[name] = moduleObj;
    }

    this.addModules = (modules) => {
        this.config.modules = Object.assign(
            {},
            this.config.modules,
            modules
        )
    }
}

function generateSelector(selector, store, o = Object.create(null), scopeName) {
    for (let name in selector) {
        if (typeof selector[name] === 'function') {
            Object.defineProperty(o, name, {
                get() {
                    if (!store.selectorCaches) {
                        store.selectorCaches = {}
                    }
                    const { selectorCaches } = store
                    const state = store.getState();
                    const key = scopeName ? `${scopeName}.${name}` : name
                    if (selectorCaches[key]) {
                        let changed = false;
                        for (let i = 0; i < selectorCaches[key].length; i++) {
                            const selectFuc = selectorCaches[key][i]
                            const paramValue = selectFuc(scopeName ? state[scopeName] : state, scopeName && state)
                            if (selectFuc.value !== paramValue) {
                                changed = true
                                selectFuc.value = paramValue
                            }
                        }
                        if (!changed) {
                            return selectorCaches[name].value
                        }
                    }
                    const value = selector[name](select.bind({
                        name: scopeName ? `${scopeName}.${name}` : name,
                        store,
                        state: scopeName ? state[scopeName] : state,
                        rootState: scopeName && state
                    }))
                    if (!selectorCaches[name]) {
                        selectorCaches[name] = []
                    }
                    selectorCaches[name].value = value;
                    return value
                }
            })
        } else {
            o[name] = Object.create(null)
            generateSelector(selector[name], store, o[name], name)
        }
    }
    return o
}

const selectInState = (keyPath) => {
    return (state) => {
        keyPath.split('.').reduce((value, key) => {
            return value[key]
        },state)
    }
}

function select(stateMapFuc) {
    if (typeof stateMapFuc === 'string') {
        stateMapFuc = selectInState(stateMapFuc)
    }
    const { name, store, state, rootState } = this
    if (!store.selectorCaches) {
        store.selectorCaches = {}
    }
    if (!store.selectorCaches[name]) {
        store.selectorCaches[name] = []
    }
    let paramValue;
    const cacheFuc = store.selectorCaches[name]
        .find(fuc => (fuc === stateMapFuc || fuc.toString() === stateMapFuc.toString()))
    if (cacheFuc) {
        return cacheFuc.value;
    } else {
        paramValue = stateMapFuc(state, rootState)
        stateMapFuc.value = paramValue;
        store.selectorCaches[name].push(stateMapFuc)
        return paramValue;
    }
}

function generateActions(actionsScope, dispatch, actions = {}) {
    Object.keys(actionsScope).forEach(key => generateAction({key, actionsScope, dispatch, actions}))
    return actions;
}

function generateAction({key, actionsScope, dispatch, actions}) {
    if (typeof actionsScope[key] === 'string') {
        const type = actionsScope[key]
        actions[key] = (payload, meta) => {
            return dispatch(type, payload, meta)
        }
    } else if (typeof actionsScope[key] === 'object') {
        Object.keys(actionsScope[key]).forEach(name => {
            const type = actionsScope[key][name];
            if (!actions[key]) {
                actions[key] = {};
            }
            actions[key][name] = (payload, meta) => {
                return dispatch(type, payload, meta)
            }
        })
    }
}

/**
 * 创建唯一的根reducer
 * @param {array} baseReducers 所有的基础recuder
 * @param {object} moduleReducers 所有具有命名空间的reducer
 */
function createRootReducer(baseReducers = [], moduleReducers = {}) {
    return function(state, action) {
            state = baseReducers.reduce((state, reducer) => {
                return reducer(state, action)
            }, state)
    
            state = combineReducers(moduleReducers)(state, action)
            
            return state
    }
}

/**
 * 解析实例化Store的配置项
 * @param {object} config
 */
function resolveStoreConfig({
    state,
    watch,
    effects,
    modules,
    selector,
    reducers,
    enhancers,
    baseReducer,
    middlewares,
}) {
    if (modules.root) {
        alert('请不要定义名为root的module，root为内部保留字！')
    }

    modules.root = {
        state,
        baseReducer,
        reducers,
        effects,
        watch,
        selector,
        middlewares,
        enhancers,
    }

    // 处理modules内部逻辑
    return Object.keys(modules).reduce((result, key) => {
        return resolveModel(key, modules[key], result)
    }, {})
}

function resolveModel(key, model, resultMap = {}) {
    const {
        preloadedState = {},
        moduleReducers = {},
        baseReducers = [],
        actionTypes = {},
        middlewares = [],
        enhancers = [],
        watchers = {},
        actions = {},
        selector = {}
    } = resultMap
    if (Array.isArray(model.middlewares)) {
        middlewares.push(...model.middlewares)
    }
    if (Array.isArray(model.enhancers)) {
        enhancers.push(...model.enhancers)
    }
    if (model.namespaced) {
        if (typeof model.state !== 'undefined') {
            if (typeof module.state !== 'object') {
                preloadedState[key] = module.state
            } else {
                preloadedState[key] = Object.assign({}, preloadedState[key], module.state) 
            }
        }

        actions[key] = {};
        if (typeof model.reducers === 'object') {
            Object.keys(model.reducers).forEach(name => {
                const type = `${key}/${name}`
                if (valiActionType(type, actionTypes)) {
                    actionTypes[type] = type
                    actions[key][name] = type
                }
            })
        }
        if (typeof model.effects === 'object') {
            Object.keys(model.effects).forEach(name => {
                const type = `${key}/${name}`
                if (valiActionType(type, actionTypes)) {
                    actionTypes[type] = model.effects[name]
                    actions[key][name] = type
                }
            })
        }
        if (typeof model.watch === 'object') {
            Object.keys(model.watch).forEach(name => {
                const watcherKey = `${key}.${name}`
                if (valiWatcherKey(watcherKey, watchers)) {
                    watchers[watcherKey] = model.watch[name]
                    watchers[watcherKey].namespace = key
                }
            })
        }

        moduleReducers[key] = (state = model.state, action) => {
            if (typeof model.baseReducer === 'function') {
                state = model.baseReducer(state, action);
            }
            if (typeof model.reducers === 'object') {
                const { type, payload, meta } = action;
                if (typeof type === 'string') {
                    const typeArr = type.split('/')
                    if (
                        typeArr[0] === key &&
                        model.reducers[typeArr[1]] &&
                        typeof model.reducers[typeArr[1]] === 'function'
                    ) {
                        state = model.reducers[typeArr[1]](state, payload, meta)
                    }
                }
            }
            return state
        }
        if (typeof model.selector === 'object') {
            for (let name in model.selector) {
                if (!selector[key]) {
                    selector[key] = {}
                }
                selector[key][name] = model.selector[name]
            }
        }
    } else {
        if (typeof model.state !== 'undefined') {
            if (preloadedState && typeof model.state !== 'object') {
                alert('module定义在根上的初始state不为object类型，并与其他module的初始state定义发生了冲突！')
            }
            if (typeof model.state === 'object') {
                for (let key in model.state) {
                    preloadedState[key] = model.state[key]
                }
            }
        }

        if (typeof model.baseReducer === 'function') {
            baseReducers.push(model.baseReducer)
        }
        if (typeof model.reducers === 'object') {
            Object.keys(model.reducers).forEach(name => {
                if (valiActionType(name, actionTypes)) {
                    actionTypes[name] = name;
                    actions[name] = name;
                }

                baseReducers.push((state = model.state, action) => {
                    if (action.type === name) {
                        return model.reducers[name](state, action.payload, action.meta)
                    }
                    return state;
                })
            })
        }
        if (typeof model.effects === 'object') {
            Object.keys(model.effects).forEach(name => {
                if (valiActionType(name, actionTypes)) {
                    actionTypes[name] = model.effects[name]
                    actions[name] = name;
                }
            })
        }
        if (typeof model.watch === 'object') {
            Object.keys(model.watch).forEach(name => {
                if (valiWatcherKey(name, watchers)) {
                    watchers[name] = model.watch[name];
                }
            })
        }
        if (typeof model.selector === 'object') {
            for (let name in model.selector) {
                selector[name] = model.selector[name]
            }
        }
    }

    return {
        selector,
        actions,
        watchers,
        enhancers,
        actionTypes,
        middlewares,
        baseReducers,
        moduleReducers,
        preloadedState,
    }
}

function valiActionType(type, actionTypes) {
    if (actionTypes[type]) {
        alert(`已经定义了类型为${type}的Action，请进行处理以防止重复定义Action！`)
        return false
    } else {
        return true
    }
}

function valiWatcherKey(key, watchers) {
    if (watchers[key]) {
        alert(`${key}已经别watcher监控，请进行处理以避免重复监控发生！`)
        return false
    } else {
        return true
    }
}

export function getStateIn(state, path) {
    let value = state;
    const pathArr = path.split('.');
    try {
        for (let i = 0; i < pathArr.length; i++) {
            const key = pathArr[i]
            if (key.charAt(0) !== '[') {
                value = value[pathArr[i]]
            }
        }
    } catch (error) {
        return undefined;
    }
    return value;
}

export function getValuesInState(state, pathArr) {
    let ret = {};
    if (Array.isArray(pathArr)) {
        ret = pathArr.reduce((ret, key) => {
            ret[key] = getStateIn(state, key)
            return ret
        }, ret)
    }
    return ret;
}

export function mapActions(map) {
    return (dispatch) => {
        const actions = {};
        Object.keys(map).forEach(name => {
            if (typeof map[name] === 'string') {
                const type = map[name];
                actions[name] = (payload, meta) => dispatch(type, payload, meta)
            } else if (typeof map[name] === 'function') {
                actions[name] = (payload, meta) => map[name](dispatch)(payload, meta)
            }
        })
        return actions;
    }
}

export function mapState(map) {
    return (state) => {
        const stateMap = {};
        Object.keys(map).forEach(name => {
            let value;
            if (typeof map[name] === 'string') {
                value = getStateIn(state, map[name])
            } else if (typeof map[name] === 'function') {
                value = map[name](state)
            }
            stateMap[name] = value
        })
        return stateMap
    }
}

export const useStore = useReduxStore
export const useSelector = useReduxSelector

export const useDispatch = (type) => {
    const dispatch = useReduxDispatch()
    if (typeof type === 'string') {
        return (payload) => dispatch(type, payload)
    } else {
        return (type, payload) => dispatch(type, payload)
    }
}

export const useMapActions = (map) => {
    const dispatch = useDispatch()
    return mapActions(map)(dispatch)
}

export const useMapState = (map) => {
    return useSelector(mapState(map))
}

export default ReactStore